#include <string>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
using namespace std;

SSL_CTX *ctx = NULL;

bool InitSSL(const char* cert, const char* key, const char* passwd) {
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ERR_load_BIO_strings();

    ctx = SSL_CTX_new(SSLv23_server_method());
    if (!ctx) {
        ERR_print_errors_fp(stderr);
        return false;
    }

    // 不要求客户端证书
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

    // 加载服务器证书
    if (SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        return false;
    }

    // 加载私钥
    SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)passwd);
    if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        return false;
    }

    // 验证私钥和证书匹配
    if (!SSL_CTX_check_private_key(ctx)) {
        ERR_print_errors_fp(stderr);
        return false;
    }

    printf("SSL initialization successful\n");
    return true;
}

int main(int argc, char* argv[]) {
    // 根据你的文件调整路径
    string cert = "makaka.asia.pem"; // 替换为你的 .pem 文件名
    string key = "makaka.asia.key";      // 替换为你的 .key 文件名
    string passwd = "";             // 如果无密码，改为 ""

    if (!InitSSL(cert.c_str(), key.c_str(), passwd.c_str())) {
        printf("init ssl error\n");
        return 1;
    }

    if (argc < 3) {
        printf("need ip-address port\n");
        return 1;
    }

    char *ip = argv[1];
    int port = atoi(argv[2]);
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        perror("Create socket error");
        return 1;
    }

    struct sockaddr_in m_addr = {0};
    m_addr.sin_family = AF_INET;
    if (inet_pton(AF_INET, ip, &m_addr.sin_addr) <= 0) {
        printf("Invalid IP address: %s\n", ip);
        return 1;
    }
    m_addr.sin_port = htons(port);

    if (bind(listenfd, (struct sockaddr*)&m_addr, sizeof(m_addr)) == -1) {
        perror("Socket bind error");
        close(listenfd);
        return 1;
    }

    if (listen(listenfd, 100) == -1) {
        perror("Listen error");
        close(listenfd);
        return 1;
    }
    printf("Server listening on %s:%d\n", ip, port);

    while (1) {
        struct sockaddr_in addr;
        socklen_t addrlen = sizeof(addr);
        int new_con = accept(listenfd, (sockaddr *)&addr, &addrlen);
        if (new_con == -1) {
            perror("accept error");
            continue;
        }
        printf("accept %d success from %s\n", new_con, inet_ntoa(addr.sin_addr));

        SSL *ssl = SSL_new(ctx);
        if (!ssl) {
            printf("SSL_new failed\n");
            ERR_print_errors_fp(stderr);
            close(new_con);
            continue;
        }
        SSL_set_fd(ssl, new_con);
        SSL_set_accept_state(ssl);

        int ret = SSL_accept(ssl);
        if (ret != 1) {
            printf("SSL_accept failed: %s\n", SSL_state_string_long(ssl));
            printf("ret = %d, ssl get error %d\n", ret, SSL_get_error(ssl, ret));
            ERR_print_errors_fp(stderr);
            SSL_free(ssl);
            close(new_con);
            continue;
        }

        string html_file = "welcome.html";
        int fd = open(html_file.c_str(), O_RDONLY);
        if (fd == -1) {
            perror("open welcome.html failed");
            SSL_shutdown(ssl);
            SSL_free(ssl);
            close(new_con);
            continue;
        }
        struct stat file_stat;
        if (stat(html_file.c_str(), &file_stat) == -1) {
            perror("stat failed");
            close(fd);
            SSL_shutdown(ssl);
            SSL_free(ssl);
            close(new_con);
            continue;
        }
        void *html_ = mmap(nullptr, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (html_ == MAP_FAILED) {
            perror("mmap failed");
            close(fd);
            SSL_shutdown(ssl);
            SSL_free(ssl);
            close(new_con);
            continue;
        }

        string buf_w = "HTTP/1.1 200 OK\r\n"
                       "Content-Type: text/html; charset=UTF-8\r\n"
                       "Connection: close\r\n"
                       "Content-Length: " + to_string(file_stat.st_size) + "\r\n"
                       "\r\n";
        buf_w += (char *)html_;

        int bytes_sent = SSL_write(ssl, buf_w.c_str(), buf_w.size());
        printf("send %d bytes\n", bytes_sent);
        if (bytes_sent <= 0) {
            printf("SSL_write failed: %d\n", SSL_get_error(ssl, bytes_sent));
            ERR_print_errors_fp(stderr);
        }

        munmap(html_, file_stat.st_size);
        close(fd);
        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(new_con);
    }

    SSL_CTX_free(ctx);
    return 0;
}